package com.wpam.scrabble.mobile.communication.bt;

import java.io.IOException;
import java.util.ArrayList;

import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.AsyncTask;
import android.util.Log;

import com.wpam.scrabble.mobile.GameLogic;
import com.wpam.scrabble.mobile.activities.PlayerInfo;
import com.wpam.scrabble.mobile.activities.PlayersWaitingActivity;
import com.wpam.scrabble.mobile.activities.PlayersWaitingActivity.UserAdapter;
import com.wpam.scrabble.mobile.communication.CommunicationManager;
import com.wpam.scrabble.mobile.messages.InfoMessage;
import com.wpam.scrabble.mobile.messages.Message;
import com.wpam.scrabble.mobile.messages.MsgSerializer;

public class BtServerManager {
	
	private BluetoothServerSocket btServerSocket_;
	private ArrayList<DeviceNetworkInfo> connectedClients_;
	private Activity parentActivity_;
	private ArrayList<AsyncTask<?,?,?>> runningTasks_;
	private boolean inGameAcceptorRunning_;

	public BtServerManager(Activity parentActivity) {
		
		btServerSocket_ = null;
		connectedClients_ = new ArrayList<DeviceNetworkInfo>();
		inGameAcceptorRunning_ = false;
		parentActivity_ = parentActivity;
		runningTasks_ = new ArrayList<AsyncTask<?,?,?>>();
	}

	void stopRunningTasks() {
		
		synchronized (runningTasks_) {
			for (AsyncTask<?,?,?> task : runningTasks_) {
				task.cancel(true);
			}
		}
		if (btServerSocket_ != null) {
			try {
				btServerSocket_.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			btServerSocket_ = null;
		}
	}

	void stopAcceptorTask() {
	
		synchronized(runningTasks_) {
			for(AsyncTask<?,?,?> task : runningTasks_) {
				if(task instanceof AcceptorTask)
					task.cancel(true);
			}
		}
		if (btServerSocket_ != null) {
			try {
				btServerSocket_.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			btServerSocket_ = null;
		}
	}
	
	private void addToRunningTasks(AsyncTask<?,?,?> task) {
		
		synchronized (runningTasks_) {
			runningTasks_.add(task);
		}
	}
	
	private void removeFromRunningTasks(AsyncTask<?,?,?> task) {
		
		synchronized (runningTasks_) {
			runningTasks_.remove(task);
		}
	}

	boolean removeFromConnectedClients(BluetoothDevice o) {
		
		for (DeviceNetworkInfo device : connectedClients_)
			if (device.getSock().getRemoteDevice().getAddress().equals(o.getAddress())) {
				connectedClients_.remove(device);
				return true;
			}
		return false;
	}

	void removeFromConnectedClients(DeviceNetworkInfo o) {
		connectedClients_.remove(o);
	}

	public void write(DeviceNetworkInfo receiver, byte[] rawBuffer) {
		
		try {
			receiver.getOutputStream().write(rawBuffer);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void write(BluetoothDevice receiver, byte[] rawBuffer) throws Exception {
		
		for (DeviceNetworkInfo device : connectedClients_) {
			if (device.equals(receiver)) {
				try {
					device.getOutputStream().write(rawBuffer);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return;
			}
		}		
		throw new Exception("Receiver not found");
	}
	
	public void writeToAll(byte[] rawBuffer) {
		
		for (DeviceNetworkInfo device : connectedClients_) {
			try {
				device.getOutputStream().write(rawBuffer);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public ArrayList<DeviceNetworkInfo> getConnectedPlayers() {
		return connectedClients_;
	}

	public boolean isPlayerConnected(String name) {
		
		for (DeviceNetworkInfo device : connectedClients_) {
			if (GameLogic.getInstance().getPlayerManager().getNameOfSender(device).equals(name))
				return true;
		}
		return false;
	}

	public void disconnectGently() {
		writeToAll(MsgSerializer.serialize(new Message(Message.GOODBYE)));
	}

	void disconnectAllClients() {
		
		for (DeviceNetworkInfo info : connectedClients_)
			BluetoothManager.getInstance().closeConnection(info);
		connectedClients_.clear();
	}
	
	public void startInGameAcceptor() {
		
		if (!inGameAcceptorRunning_) {
			InGameAcceptorTask task = new InGameAcceptorTask();
			task.execute();
		}
	}
	
	void stopInGameAcceptorTask() {
		
		synchronized(runningTasks_) {
			for(AsyncTask<?,?,?> task : runningTasks_) {
				if(task instanceof InGameAcceptorTask)
					task.cancel(true);
			}
		}
		if (btServerSocket_ != null) {
			try {
				btServerSocket_.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			btServerSocket_ = null;
		}
	}
	
	/////////////////////////////////////////////////////////////////////////////////
	//////////////// TASKS
	/////////////////////////////////////////////////////////////////////////////////
	
	class AcceptorTask extends AsyncTask<Void, BluetoothSocket, Void> {
		
		private UserAdapter adapter_ = ((PlayersWaitingActivity)parentActivity_).getUserAdapter();
		
		@Override
		protected void onPreExecute() {
			
			super.onPreExecute();
			addToRunningTasks(this);
		}
		
		@Override
		protected Void doInBackground(Void... params) {

			inGameAcceptorRunning_ = true;
			CommunicationManager.getInstance(parentActivity_).startServerMsgParser();
			try {
				btServerSocket_ = BluetoothManager.getInstance().getAdapter().listenUsingRfcommWithServiceRecord("Scrabble Mobile", BluetoothManager.MY_UUID);
				while(!isCancelled()) {
					BluetoothSocket btSocket = null;
					try {
						btSocket = btServerSocket_.accept();

						DeviceNetworkInfo info = new DeviceNetworkInfo(btSocket, btSocket.getInputStream(), btSocket.getOutputStream());
						connectedClients_.add(info);
						
						ReaderTask task = new ReaderTask();
						task.execute(info);
					}
					catch (IOException e) {
						Log.i("SCRABBLE_SERVER", "AcceptorTask interrupted. isCancelled = " + Boolean.toString(isCancelled()));
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				try {
					if (btServerSocket_ != null)
						btServerSocket_.close();
					btServerSocket_ = null;
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			Log.i("SCRABBLE_SERVER", "AcceptorTask ended.");
			return null;
		}

		@Override
		protected void onProgressUpdate(BluetoothSocket... values) {
			
			super.onProgressUpdate(values);
			synchronized (adapter_) {
				adapter_.add(new PlayerInfo(values[0].getRemoteDevice()));
				adapter_.notifyDataSetChanged();
			}
		}

		@Override
		protected void onPostExecute(Void result) {

			super.onPostExecute(result);
			removeFromRunningTasks(this);
		}
	}
	
	private class InGameAcceptorTask extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
			
			super.onPreExecute();
			addToRunningTasks(this);
		}
		
		@Override
		protected Void doInBackground(Void... arg0) {
			try {
				btServerSocket_ = BluetoothManager.getInstance().getAdapter().listenUsingRfcommWithServiceRecord("Scrabble Mobile", BluetoothManager.MY_UUID);
				while(!isCancelled()) {
					BluetoothSocket btSocket = null;
					try {
						btSocket = btServerSocket_.accept();

						DeviceNetworkInfo info = new DeviceNetworkInfo(btSocket, btSocket.getInputStream(), btSocket.getOutputStream());
						if (!GameLogic.getInstance().canPlayerJoinGame(info.getSock().getRemoteDevice())) {
							// Refuse connection
							CommunicationManager.writeAsServer(info, MsgSerializer.serialize(new InfoMessage()));
							BluetoothManager.getInstance().closeConnection(info);
						}
						else {
							connectedClients_.add(info);
							
							ReaderTask task = new ReaderTask();
							task.execute(info);
						}
					}
					catch (IOException e) {
						Log.i("SCRABBLE_SERVER", "InGameAcceptorTask interrupted. isCancelled = " + Boolean.toString(isCancelled()));
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				try {
					if (btServerSocket_ != null)
						btServerSocket_.close();
					btServerSocket_ = null;
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			Log.i("SCRABBLE_SERVER", "InGameAcceptorTask ended.");
			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			
			super.onPostExecute(result);
			inGameAcceptorRunning_ = false;
			removeFromRunningTasks(this);
		}
		
	}
	
	private class ReaderTask extends AsyncTask<DeviceNetworkInfo, Void, Void> {

    	private byte[] rawBuffer_ = new byte[BluetoothManager.BUFFER_SIZE];
    	private DeviceNetworkInfo info_;
		private UserAdapter adapter_ = ((PlayersWaitingActivity)parentActivity_).getUserAdapter();
    	
		@Override
		protected void onPreExecute() {
			
			super.onPreExecute();
			addToRunningTasks(this);
		}
		
		@Override
		protected Void doInBackground(DeviceNetworkInfo... params) {
			info_ = params[0];
			
			while (!isCancelled()) {
				synchronized (info_.getInputStream()) {
					try {
						info_.getInputStream().read(rawBuffer_);
						Message msg = MsgSerializer.deserialize(rawBuffer_);
						msg.setSenderDevice(info_.getSock().getRemoteDevice());
						CommunicationManager.getInstance().getMessageBuffer().put(msg);
					} catch (IOException e) {
						BluetoothManager.getInstance().closeConnection(info_);
						return null;
					}
				}
			}
			Log.i("SCRABBLE_SERVER", "ReaderTask ended.");
			return null;
		}

		@Override
		protected void onPostExecute(Void result) {

			super.onPostExecute(result);
			synchronized(adapter_) {
				adapter_.remove(new PlayerInfo(info_.getSock().getRemoteDevice()));
				adapter_.notifyDataSetChanged();
			}
			connectedClients_.remove(info_);
			BluetoothManager.getInstance().closeConnection(info_);
			removeFromRunningTasks(this);
		}

		@Override
		protected void onCancelled() {

			BluetoothManager.getInstance().closeConnection(info_);
			super.onCancelled();
		}
    }

}
